package com.tuniondata.netty.common.protocol;

import com.tuniondata.netty.bean.ApiBinaryBean;
import com.tuniondata.netty.bean.ApiBinaryDTO;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Arrays;

/**
 * @author yuxinchao
 */
public class Protocol {
    private static final Logger log = LoggerFactory.getLogger(Protocol.class);

    private int length; // 包长度
    private String syncData; // 同步信息
    private int zipFlag; // 压缩标志
    private int encAlgorithm; // 加密算法
    private int version; // 版本号
    private String messageType; //消息类型

    private String body; // 报文体

    private String mac; // MAC

    public Protocol() {
    }

    public Protocol(byte[] data) throws ApiProtocolException {
        try {
            unpackHeader(data);

            String dataString = new String(data, "GBK");
            this.body = dataString.substring(24, getLength() + 4 -16);
            this.mac = dataString.substring(24 + this.body.length(), dataString.length());

            log.info("body: {}, mac: {}", body, mac);
        } catch (Exception e) {
            throw new ApiProtocolException(e);
        }
    }

    public Protocol(ApiBinaryBean bean) throws ApiProtocolException {
        pack(bean);
    }

    public byte[] toByteArray() throws IOException {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(String.format("%04d", this.length))
            .append(String.format("%1$-12s", this.syncData))
            .append(this.zipFlag)
            .append(this.encAlgorithm)
            .append(String.format("%02d", this.version))
            .append(String.format("%1$-4s", this.messageType))
            .append(this.body == null ? "" : this.body)
            .append(this.mac == null ? "" : this.mac);

        log.info("the protocol pack string is {}", stringBuffer.toString());

        return stringBuffer.toString().getBytes("GBK");
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public String getSyncData() {
        return syncData;
    }

    public void setSyncData(String syncData) {
        this.syncData = syncData;
    }

    public int getZipFlag() {
        return zipFlag;
    }

    public void setZipFlag(int zipFlag) {
        this.zipFlag = zipFlag;
    }

    public int getEncAlgorithm() {
        return encAlgorithm;
    }

    public void setEncAlgorithm(int encAlgorithm) {
        this.encAlgorithm = encAlgorithm;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public String getMessageType() {
        return messageType;
    }

    public void setMessageType(String messageType) {
        this.messageType = messageType;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getMac() {
        return mac;
    }

    public void setMac(String mac) {
        this.mac = mac;
    }

    public String pack(ApiBinaryBean bean) throws ApiProtocolException {
        log.info("enter pack method...");
        ApiBinaryDTO annotation = getAnnotation(bean.getClass());
        StringBuffer stringBuffer = new StringBuffer();
        StringBuffer content = new StringBuffer();
        this.version = annotation.ver();
        this.messageType = annotation.mtp();
        this.encAlgorithm = annotation.encAlgorithm();
        this.syncData = annotation.sync();
        this.zipFlag = annotation.zipFlag();
        this.mac = annotation.mac();
        try {
            this.body = new String(bean.pack(), "GBK");

            stringBuffer.append(String.format("%1$-12s", this.syncData))
                        .append(this.zipFlag)
                        .append(this.encAlgorithm)
                        .append(String.format("%02d", this.version))
                        .append(String.format("%1$-4s", this.messageType))
                        .append(this.body == null ? "" : this.body)
                        .append(this.mac == null ? "" : this.mac);

            this.length = stringBuffer.toString().length(); // 整个包的长度

            content.append(String.format("%04d", this.length))
                .append(stringBuffer);

            log.info("the pack string is {}", content.toString());

            return content.toString();
        } catch (Exception e) {
            throw new ApiProtocolException(e);
        }
    }

    public Protocol unpack(String data) throws ApiProtocolException {
        try {
            byte[] content = data.getBytes("GBK");
            unpackHeader(content);

            String dataString = new String(content);
            this.body = dataString.substring(24, (getLength() + 4 - 16));
            this.mac = dataString.substring(24 + this.body.length(), dataString.length());

            log.info("body: {}, mac: {}", new String(body.getBytes("GBK")), mac);

            return this;
        } catch (Exception e) {
            e.printStackTrace();
            throw new ApiProtocolException(e);
        }
    }

    private ApiBinaryDTO getAnnotation(Class<?> clazz) throws ApiProtocolUnsupportedDTOException {
        ApiBinaryDTO annotation = clazz.getAnnotation(ApiBinaryDTO.class);
        if (annotation == null) {
            throw new ApiProtocolUnsupportedDTOException(
                String.format("unsupported type: %s", clazz.getCanonicalName())
            );
        }
        return annotation;
    }

    public void unpackHeader(byte[] content) throws Exception{
        String data = new String(content, "GBK");
        this.length = Integer.valueOf(data.substring(0, 4));
        this.syncData = data.substring(4, 16);
        this.zipFlag = Integer.valueOf(data.substring(16, 17));
        this.encAlgorithm = Integer.valueOf(data.substring(17, 18));
        this.version = Integer.valueOf(data.substring(18, 20));
        this.messageType = data.substring(20, 24);

        log.info("length:{}, syncData:{}, zipFlag:{}, encAlgorithm:{}, version:{}, messageType:{}",
            this.length, this.syncData, this.zipFlag, this.encAlgorithm,
            this.version, this.messageType);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
    }

}
